package com.hay.oss.ftp;

import com.hay.oss.OssConstant;
import com.hay.oss.OssTemplate;
import com.hay.oss.bo.OssFile;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

import java.io.InputStream;
import java.util.Objects;

/**
 * ftp 功能模版
 * @author LiMouRen
 * @date 2024/5/10
 */
@Slf4j
public class FtpOssTemplate implements OssTemplate {

    private final FtpOssProperties ftpProperties;

    public FtpOssTemplate(FtpOssProperties ftpProperties){
        this.ftpProperties = ftpProperties;
    }

    /**
     * 上传文件
     * @param category 文件前缀,如 product/20240115
     * @param originalFileName 文件原名
     * @param input 流
     * @param useOriginalName true 使用原名
     */
    @Override
    public OssFile uploadFile(String category, String originalFileName, InputStream input, Boolean useOriginalName){
        String relativeUrl = createRelativeUrl(category,originalFileName,useOriginalName);
        String fileAbsolutePath = ftpProperties.getBucketName() + OssConstant.SLASH + relativeUrl;
        String dirPath = fileAbsolutePath.substring(0,fileAbsolutePath.lastIndexOf("/"));
        boolean uploadSuccess = false;
        FTPClient ftpClient = null;
        String replyString = null;
        try {
            ftpClient = getConnect();
            // 创建目录
            makeDirs(ftpClient,dirPath);
            // 上传
            uploadSuccess = ftpClient.storeFile(fileAbsolutePath, input);
            replyString = ftpClient.getReplyString();
            if (!uploadSuccess){
                throw new RuntimeException("FTP文件上传失败 "+replyString);
            }
        }catch (Exception e){
            log.error("FTP uploadFile fail :fileAbsolutePath:{},dirPath:{},replyString:{},error:{}",fileAbsolutePath,dirPath,replyString,e);
            throw new RuntimeException(e.getMessage());
        } finally {
            closeConnect(ftpClient);
        }
        String link = ftpProperties.getProxyPrefix() + ftpProperties.getBucketName() + OssConstant.SLASH + relativeUrl;
        OssFile ossFile = new OssFile();
        ossFile.setLink(link);
        ossFile.setOriginalName(originalFileName);
        ossFile.setRelativeUrl(relativeUrl);
        return ossFile;
    }

    @Override
    public InputStream getInputStream(String relativeUrl) {
        FTPClient ftpClient = getConnect();
        String fileAbsolutePath = ftpProperties.getBucketName() + OssConstant.SLASH + relativeUrl;
        String replyString = null;
        try {
            return ftpClient.retrieveFileStream(fileAbsolutePath);
        }catch (Exception e){
            log.error("FTP getInputStream fail :fileAbsolutePath:{},replyString:{},error:{}",fileAbsolutePath,replyString,e);
            throw new RuntimeException(e.getMessage());
        } finally {
            closeConnect(ftpClient);
        }
    }

    @Override
    public void removeFile(String relativeUrl) {
        FTPClient ftpClient = getConnect();
        String fileAbsolutePath = ftpProperties.getBucketName() + OssConstant.SLASH + relativeUrl;
        String replyString = null;
        try {
            ftpClient.deleteFile(fileAbsolutePath);
            replyString = ftpClient.getReplyString();
        }catch (Exception e){
            log.error("FTP removeFile fail :fileAbsolutePath:{},replyString:{},error:{}",fileAbsolutePath,replyString,e);
            throw new RuntimeException(e.getMessage());
        } finally {
            closeConnect(ftpClient);
        }
    }


    public FTPClient getConnect() {
        if (Objects.equals(ftpProperties.getEnable(),Boolean.FALSE)){
            throw new RuntimeException("ftp is not enable");
        }
        try {
            FTPClient ftpClient = new FTPClient();
            ftpClient.setDataTimeout(1200);
            // 连接FTP服务器
            ftpClient.connect(ftpProperties.getHost(), ftpProperties.getPort());
            // 登陆FTP服务器
            ftpClient.login(ftpProperties.getUsername(), ftpProperties.getPassword());
            ftpClient.setBufferSize(1024 * 1024 * 10);
            // 设置传输的模式，以二进制流的方式读取
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            // 中文支持
            ftpClient.setControlEncoding("UTF-8");
            ftpClient.enterLocalPassiveMode();
            if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
                ftpClient.disconnect();
                throw new RuntimeException("FTP连接失败");
            }
            return ftpClient;
        } catch (Exception e) {
            log.error("FTP链接失败，请检查FTP相关配置信息是否正确！ftpProperties:{},error:{}", ftpProperties,e.getMessage(),e);
            return null;
        }
    }

    public void closeConnect(FTPClient ftpClient) {
        try {
            if (Objects.isNull(ftpClient)){
                return;
            }
            if (!ftpClient.logout()){
                ftpClient.logout();
            }
            if(ftpClient.isConnected()){
                ftpClient.disconnect();
            }
        }catch (Exception e){
            log.error("FTP链接关闭失败，ftpProperties:{},error:{}", ftpProperties,e.getMessage(),e);
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 创建目录
     * @param ftpClient 客户端连接
     * @param dirPath 目录路径 如 /upload/product
     */
    public void makeDirs(FTPClient ftpClient,String dirPath){
        try {
            // 进入目录
            boolean changeWorkingDirectory = ftpClient.changeWorkingDirectory(dirPath);
            if (changeWorkingDirectory){
               return;
            }
            for (String partTargetPath : dirPath.split(OssConstant.SLASH)) {
                if (partTargetPath.equals(OssConstant.EMPTY) || ftpClient.changeWorkingDirectory(partTargetPath)){
                    continue;
                }
                boolean flag = ftpClient.makeDirectory(partTargetPath);
                if (!flag) {
                    throw new RuntimeException("创建目录失败");
                }
                ftpClient.changeWorkingDirectory(partTargetPath);
            }
        }catch (Exception e){
            log.error("FTP makeDirs fail : dirPath:{},error:{}",dirPath,e.getMessage(),e);
            throw new RuntimeException(e.getMessage());
        }
    }
}
